package jp.co.nes.awssupport.business.utility;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.nes.awssupport.business.bean.BillingCsvBean;
import jp.co.nes.awssupport.business.constant.AwsProductName;
import jp.co.nes.awssupport.business.constant.Duration;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.ReservedManageBean;
import jp.co.nes.awssupport.common.utility.AppInfoUtil;

/**
 * 使用タイプユーティリティです。
 */
public class UsageTypeUtil {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(UsageTypeUtil.class);

    /** m1.small */
    private static final String M1_SMALL = "m1.small";

    /** elasticsearch */
    private static final String ELASTICSEARCH = "elasticsearch";

    /** 1year */
    private static final String ONE_YEAR = "1year";

    /** 3year */
    private static final String THREE_YEAR = "3year";

    /** HeavyUsage */
    private static final String HEAVY_USAGE = "HeavyUsage";

    /** BoxUsage */
    private static final String BOX_USAGE = "BoxUsage";

    /** NodeUsage */
    private static final String NODE_USAGE = "NodeUsage";

    /** ESInstance */
    private static final String ESINSTANCE = "ESInstance";

    /** KMS-Requests */
    private static final String KMS_REQUESTS = "KMS-Requests";

    /** KMS-Keys */
    private static final String KMS_KEYS = "KMS-Keys";

    /** オンデマンドインスタンスのUsageType情報欠落補完(EC2用)で使用するPatternクラスの配列 */
    private static Pattern[] mcPatterns;

    /** 同じUsageTypeで区別する必要のあるものの補完で使用するPatternクラスの配列を持つマップを含むマップ */
    private static Map<AwsProductName, Map<OcPatternsKind, Pattern[]>> ocPatterns;

    /** リージョン名をリージョンコード変換(KMS-Requests用)で使用するPatternクラスの配列 */
    private static Pattern[] crntrcPatternsForRequests;

    /** リージョン名をリージョンコード変換(KMS_KEYS用)で使用するPatternクラスの配列 */
    private static Pattern[] crntrcPatternsForKeys;

    /** リージョン間使用に関するUsageType変換で使用するPatternクラスの配列 */
    private static Pattern[] anotherRegionPatterns;

    /**
     * 初期化処理です。
     */
    static {
        mcPatterns = createPattern("McPatternsForEc2");
        ocPatterns = createPatternMap("OcPatterns");
        crntrcPatternsForRequests = createPattern("CrntrcPatternsForRequests");
        crntrcPatternsForKeys = createPattern("CrntrcPatternsForKeys");
        anotherRegionPatterns = createPattern("AnotherRegionPatterns");
    }

    /** 使用タイプ作成時の種別 */
    private static enum UsageTypeCreateKind {
        /** リザーブド購入(前払い料金) */
        RESERVED_UPFRONT,

        /** リザーブド定額月額料金(前払い無し)(EC2以外) */
        RESERVED_MONTHLY_FIXED_NO_UPFRONT,

        /** リザーブド(前払い無し)(EC2以外) */
        RESERVED_METERED_NO_UPFRONT
    }

    /** 同じUsageTypeで区別する必要のあるものの補完で使用する正規表現の種別 */
    private static enum OcPatternsKind {
        /** 補完するUsageTypeの正規表現 */
        INCLUSIONS,

        /** 補完を除外するUsageTypeの正規表現 */
        EXCLUSIONS
    }

    /**
     * デフォルトコンストラクタです。<br>
     * 本クラスはインスタンスを生成しません。
     */
    private UsageTypeUtil() {
    }

    /**
     * リザーブド購入(前払い料金)に関する使用タイプを返却します。<br>
     * 製品名がEC2, ElastiCache, RDS, Redshift, DynamoDB, Elasticsearchの場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedUpfront(ReservedManageBean rmBean) {
        return createUsageType(UsageTypeCreateKind.RESERVED_UPFRONT, rmBean);
    }

    /**
     * リザーブド定額月額料金(前払い無し)(EC2)に関する使用タイプを返却します。<br>
     * 製品名がEC2の場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param csvBean 請求CSV
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedMonthlyFixedNoUpfrontEc2(BillingCsvBean csvBean, ReservedManageBean rmBean) {
        String productName = csvBean.getProductName();
        String usageType = csvBean.getUsageType();

        AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
        if (awsProductName != AwsProductName.EC2) {
            throw new CommonException("productName is wrong (productName=" + productName + ")");
        }

        StringBuilder builder = new StringBuilder(usageType);
        appendM1(awsProductName, builder);
        replaceUsageType(awsProductName, BOX_USAGE, HEAVY_USAGE, builder);
        appendOfferingClass(awsProductName, rmBean.getOfferingClass(), builder);
        appendOfferingType(awsProductName, rmBean.getOfferingType(), builder);
        appendDuration(rmBean.getDuration(), builder);
        appendMonthlyStar(builder);
        return builder.toString();
    }

    /**
     * リザーブド定額月額料金(前払い無し)(EC2以外)に関する使用タイプを返却します。<br>
     * 製品名がElastiCache, RDS, Redshift, DynamoDB. Elasticsearchの場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedMonthlyFixedNoUpfront(ReservedManageBean rmBean) {
        return createUsageType(UsageTypeCreateKind.RESERVED_MONTHLY_FIXED_NO_UPFRONT, rmBean);
    }

    /**
     * リザーブド定額月額料金(前払い無し以外)に関する使用タイプを返却します。<br>
     * 製品名がEC2, ElastiCache, RDS, Redshift, DynamoDB, Elasticsearchの場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param csvBean 請求CSV
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedMonthlyFixed(BillingCsvBean csvBean, ReservedManageBean rmBean) {
        String productName = csvBean.getProductName();
        String usageType = csvBean.getUsageType();

        AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
        if (awsProductName != AwsProductName.EC2 && awsProductName != AwsProductName.RDS
                && awsProductName != AwsProductName.ELASTI_CACHE && awsProductName != AwsProductName.REDSHIFT
                && awsProductName != AwsProductName.DYNAMO_DB
                && awsProductName != AwsProductName.ELASTIC_SEARCH_SERVICE) {
            throw new CommonException("productName is wrong (productName=" + productName + ")");
        }

        StringBuilder builder = new StringBuilder(usageType);
        appendM1(awsProductName, builder);
        if (awsProductName == AwsProductName.ELASTI_CACHE) {
            replaceUsageTypeForReservedMonthlyFixed(awsProductName, HEAVY_USAGE, NODE_USAGE, builder);
        } else if (awsProductName == AwsProductName.ELASTIC_SEARCH_SERVICE) {
            replaceUsageTypeForReservedMonthlyFixed(awsProductName, HEAVY_USAGE, ESINSTANCE, builder);
        // TODO 設計上、保留になっている箇所
        //} else if (awsProductName == AwsProductName.DYNAMO_DB) {
        //    replaceUsageTypeForReservedMonthlyFixed(awsProductName, HEAVY_USAGE, "CapacityUnit", builder);
        } else {
            replaceUsageType(awsProductName, BOX_USAGE, HEAVY_USAGE, builder);
        }
        appendOfferingClass(awsProductName, rmBean.getOfferingClass(), builder);
        appendRI(awsProductName, builder);
        appendOfferingType(awsProductName, rmBean.getOfferingType(), builder);
        appendDuration(rmBean.getDuration(), builder);
        appendMonthlyStar(builder);
        return builder.toString();
    }

    /**
     * 融通リザーブド(EC2)に関する使用タイプを返却します。<br>
     * 製品名がEC2の場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param csvBean 請求CSV
     * @return 使用タイプ
     */
    public static String getForBlendedReservedEc2(BillingCsvBean csvBean) {
        String productName = csvBean.getProductName();
        String usageType = csvBean.getUsageType();

        AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
        if (awsProductName != AwsProductName.EC2) {
            throw new CommonException("productName is wrong (productName=" + productName + ")");
        }

        StringBuilder builder = new StringBuilder(usageType);
        appendM1(awsProductName, builder);
        replaceUsageType(awsProductName, HEAVY_USAGE, BOX_USAGE, builder);
        return builder.toString();
    }

    /**
     * リザーブド(前払い無し)(EC2)に関する使用タイプを返却します。<br>
     * 製品名がEC2の場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param csvBean 請求CSV
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedMeteredNoUpfrontEc2(BillingCsvBean csvBean, ReservedManageBean rmBean) {
        String productName = csvBean.getProductName();
        String usageType = csvBean.getUsageType();

        AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
        if (awsProductName != AwsProductName.EC2) {
            throw new CommonException("productName is wrong (productName=" + productName + ")");
        }

        StringBuilder builder = new StringBuilder(usageType);
        appendM1(awsProductName, builder);
        replaceUsageType(awsProductName, BOX_USAGE, HEAVY_USAGE, builder);
        appendOfferingClass(awsProductName, rmBean.getOfferingClass(), builder);
        appendOfferingType(awsProductName, rmBean.getOfferingType(), builder);
        appendDuration(rmBean.getDuration(), builder);
        appendMonthlyApplied(builder);
        return builder.toString();
    }

    /**
     * リザーブド(前払い無し)(EC2以外)に関する使用タイプを返却します。<br>
     * 製品名がElastiCache, RDS, Redshift, DynamoDB, Elasticsearchの場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedMeteredNoUpfront(ReservedManageBean rmBean) {
        return createUsageType(UsageTypeCreateKind.RESERVED_METERED_NO_UPFRONT, rmBean);
    }

    /**
     * リザーブド(前払い無し以外)に関する使用タイプを返却します。<br>
     * 製品名がEC2, ElastiCache, RDS, Redshift, DynamoDB, Elasticsearchの場合に呼び出します(左記以外の場合は例外をスローします)。
     *
     * @param csvBean 請求CSV
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    public static String getForReservedMetered(BillingCsvBean csvBean, ReservedManageBean rmBean) {
        String productName = csvBean.getProductName();
        String usageType = csvBean.getUsageType();

        AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
        if (awsProductName != AwsProductName.EC2 && awsProductName != AwsProductName.RDS
                && awsProductName != AwsProductName.ELASTI_CACHE && awsProductName != AwsProductName.REDSHIFT
                && awsProductName != AwsProductName.DYNAMO_DB
                && awsProductName != AwsProductName.ELASTIC_SEARCH_SERVICE) {
            throw new CommonException("productName is wrong (productName=" + productName + ")");
        }

        StringBuilder builder = new StringBuilder(usageType);
        appendM1(awsProductName, builder);
        appendElasticsearch(awsProductName, builder);
        replaceUsageType(awsProductName, BOX_USAGE, HEAVY_USAGE, builder);
        appendOfferingClass(awsProductName, rmBean.getOfferingClass(), builder);
        appendRI(awsProductName, builder);
        appendOfferingType(awsProductName, rmBean.getOfferingType(), builder);
        appendDuration(rmBean.getDuration(), builder);
        appendMonthlyApplied(builder);
        return builder.toString();
    }

    /**
     * 使用タイプを作成します。<br>
     * UsageTypeCreateKindが、RESERVED_UPFRONTの場合、製品名がEC2, ElastiCache, RDS, Redshift, DynamoDB, Elasticsearch以外であれば、
     * 例外を発生させます。<br>
     * UsageTypeCreateKindが、RESERVED_UPFRONT以外の場合、製品名がElastiCache, RDS, Redshift, DynamoDB, Elasticsearch以外であれば、
     * 例外を発生させます。
     *
     * @param usageTypeCreateKind 使用タイプ作成種別
     * @param rmBean リザーブド管理
     * @return 使用タイプ
     */
    private static String createUsageType(UsageTypeCreateKind usageTypeCreateKind, ReservedManageBean rmBean) {
        String productName = rmBean.getProductName();
        String regionCode = rmBean.getRegionCode();
        String instanceType = rmBean.getInstanceType();
        String offeringType = rmBean.getOfferingType();
        String offeringClass = rmBean.getOfferingClass();
        int duration = rmBean.getDuration();
        boolean isMultiAZ = false;
        String multiAZ = rmBean.getMultiAZ();
        if ("1".equals(multiAZ)) {
            isMultiAZ = true;
        }
        String productDescription = rmBean.getProductDescription();

        String usageType = null;
        if (AwsProductNameUtil.isEquals(AwsProductName.EC2, productName)) {
            if (usageTypeCreateKind == UsageTypeCreateKind.RESERVED_UPFRONT) {
                usageType = createUsageType(usageTypeCreateKind, AwsProductName.EC2, regionCode, instanceType,
                        offeringType, offeringClass, duration, productDescription, null);
            } else {
                // UsageTypeCreateKind.RESERVED_UPFRONT以外の場合は例外をスローする。
                throw new CommonException("productName is wrong (productName=" + productName + ")");
            }
        } else if (AwsProductNameUtil.isEquals(AwsProductName.ELASTI_CACHE, productName)
                || AwsProductNameUtil.isEquals(AwsProductName.REDSHIFT, productName)) {
            AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
            usageType = createUsageType(usageTypeCreateKind, awsProductName, regionCode, instanceType, offeringType,
                    null, duration, productDescription, null);
        } else if (AwsProductNameUtil.isEquals(AwsProductName.RDS, productName)) {
            usageType = createUsageType(usageTypeCreateKind, AwsProductName.RDS, regionCode, instanceType, offeringType,
                    null, duration, productDescription, isMultiAZ);
        } else if (AwsProductNameUtil.isEquals(AwsProductName.DYNAMO_DB, productName)) {
            usageType = createUsageType(usageTypeCreateKind, AwsProductName.DYNAMO_DB, regionCode, instanceType,
                    offeringType, null, duration, productDescription, null);
        } else if (AwsProductNameUtil.isEquals(AwsProductName.ELASTIC_SEARCH_SERVICE, productName)) {
            usageType = createUsageType(usageTypeCreateKind, AwsProductName.ELASTIC_SEARCH_SERVICE, regionCode,
                    instanceType, offeringType, null, duration, null, null);
        } else {
            // 製品名がEC2, ElastiCache, RDS, Redshift, DynamoDB, Elasticsearch以外は例外をスローする。
            throw new CommonException("productName is wrong (productName=" + productName + ")");
        }

        return usageType;
    }

    /**
     * 使用タイプを作成します。
     *
     * @param usageTypeCreateKind 使用タイプ作成種別
     * @param awsProductName AWSサービス製品名
     * @param regionCode リージョンコード
     * @param instanceType インスタンスタイプ
     * @param offeringType 要求タイプ
     * @param offeringClass 要求クラス
     * @param duration 期間
     * @param productDescription 製品説明
     * @param isMultiAZ マルチAZかどうか(true: マルチAZ, false: シングルAZ)
     * @return 使用タイプ
     */
    private static String createUsageType(UsageTypeCreateKind usageTypeCreateKind, AwsProductName awsProductName,
            String regionCode, String instanceType, String offeringType, String offeringClass, int duration,
            String productDescription, Boolean isMultiAZ) {
        StringBuilder builder = new StringBuilder();

        appendRegionCode(regionCode, builder);
        if (usageTypeCreateKind == UsageTypeCreateKind.RESERVED_UPFRONT) {
            appendReservedKindValue(awsProductName, isMultiAZ, builder);
            appendInstanceType(instanceType, builder);
            appendOfferingClass(awsProductName, offeringClass, builder);
            appendOfferingType(awsProductName, offeringType, builder);
            appendDuration(duration, builder);
            appendUpfront(builder);
            appendProductTypeValue(awsProductName, productDescription, builder);
        } else if (usageTypeCreateKind == UsageTypeCreateKind.RESERVED_MONTHLY_FIXED_NO_UPFRONT
                || usageTypeCreateKind == UsageTypeCreateKind.RESERVED_METERED_NO_UPFRONT) {
            appendReservedKindValueOfNoUpfront(awsProductName, isMultiAZ, builder);
            appendInstanceType(instanceType, builder);
            appendRI(awsProductName, builder);
            appendOfferingType(awsProductName, offeringType, builder);
            appendDuration(duration, builder);
            if (usageTypeCreateKind == UsageTypeCreateKind.RESERVED_MONTHLY_FIXED_NO_UPFRONT) {
                appendMonthlyStar(builder);
            } else {
                appendMonthlyApplied(builder);
            }
        } else {
            // 通常発生しない。
            throw new CommonException("usageTypeCreateKind is wrong");
        }
        return builder.toString();
    }

    /**
     * インスタンスタイプの欠落を追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendM1(AwsProductName awsProductName, StringBuilder builder) {
        boolean isAppend = false;
        if (awsProductName == AwsProductName.EC2) {
            String value = builder.toString();
            if (value.endsWith(HEAVY_USAGE) || value.endsWith(BOX_USAGE)) {
                // インスタンスタイプの欠落追加
                builder.append(":");
                builder.append(M1_SMALL);
                isAppend = true;
            }
        }
        return isAppend;
    }

    /**
     * インスタンスタイプ末尾の欠落を追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendElasticsearch(AwsProductName awsProductName, StringBuilder builder) {
        boolean isAppend = false;
        if (awsProductName == AwsProductName.ELASTIC_SEARCH_SERVICE) {
            String value = builder.toString();
            String suffix = "." + ELASTICSEARCH;
            if (value.indexOf(ESINSTANCE) >= 0 && !value.endsWith(suffix)) {
                // インスタンスタイプ末尾の欠落追加
                builder.append(suffix);
                isAppend = true;
            }
        }
        return isAppend;
    }

    /**
     * 使用タイプに含まれている文字列を置換します。
     *
     * @param awsProductName AWSサービス製品名
     * @param before 置換前の文字列
     * @param after 置換後の文字列
     * @param builder 置換するStringBuilderインスタンス
     * @return 置換したかどうか(true: 置換した, false: 置換していない)
     */
    private static boolean replaceUsageType(AwsProductName awsProductName, String before, String after,
            StringBuilder builder) {
        boolean isReplace = false;
        if (awsProductName == AwsProductName.EC2) {
            int index = builder.indexOf(before);
            if (index >= 0) {
                // UsageTypeが変換されていない場合、UsageTypeを変換
                builder.replace(index, index + before.length(), after);
                isReplace = true;
            }
        }
        return isReplace;
    }

    /**
     * 使用タイプに含まれている文字列を置換します(リザーブド定額月額料金(前払い無し以外))。
     *
     * @param awsProductName AWSサービス製品名
     * @param before 置換前の文字列
     * @param after 置換後の文字列
     * @param builder 置換するStringBuilderインスタンス
     * @return 置換したかどうか(true: 置換した, false: 置換していない)
     */
    private static boolean replaceUsageTypeForReservedMonthlyFixed(AwsProductName awsProductName, String before,
            String after, StringBuilder builder) {
        boolean isReplace = false;
        if (awsProductName == AwsProductName.ELASTI_CACHE || awsProductName == AwsProductName.ELASTIC_SEARCH_SERVICE) {
            int index = builder.indexOf(before);
            if (index >= 0) {
                // UsageTypeが変換されていない場合、UsageTypeを変換
                builder.replace(index, index + before.length(), after);
                isReplace = true;
            }
        }
        return isReplace;
    }

    /**
     * リージョンコードを追加します。
     *
     * @param regionCode リージョンコード
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendRegionCode(String regionCode, StringBuilder builder) {
        boolean isAppend = false;
        if (StringUtils.isEmpty(regionCode)) {
            logger.warn("regionCode is null or empty");
        } else {
            builder.append(regionCode);
            builder.append("-");
            isAppend = true;
        }
        return isAppend;
    }

    /**
     * リザーブド種類文字列を追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param isMultiAZ マルチAZかどうか(true: マルチAZ, false: シングルAZ)
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendReservedKindValue(AwsProductName awsProductName, Boolean isMultiAZ,
            StringBuilder builder) {
        // リザーブド種類文字列の追加
        if (AwsProductName.EC2 == awsProductName) {
            builder.append("ReservedInstance");
        } else if (AwsProductName.ELASTI_CACHE == awsProductName) {
            builder.append("ReservedCacheNode");
        } else if (AwsProductName.REDSHIFT == awsProductName) {
            builder.append("ReservedNode");
        } else if (AwsProductName.RDS == awsProductName) {
            if (isMultiAZ != null && isMultiAZ.booleanValue()) {
                builder.append("ReservedMultiAZDBInstance");
            } else {
                builder.append("ReservedSingleAZDBInstance");
            }
        } else if (AwsProductName.DYNAMO_DB == awsProductName) {
            // TODO 設計上、保留になっている箇所
            builder.append("ReservedCapacityUnit");
        } else if (AwsProductName.ELASTIC_SEARCH_SERVICE == awsProductName) {
            builder.append("ReservedESInstance");
        }
        return true;
    }

    /**
     * リザーブド種類文字列を追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param isMultiAZ マルチAZかどうか(true: マルチAZ, false: シングルAZ)
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendReservedKindValueOfNoUpfront(AwsProductName awsProductName, Boolean isMultiAZ,
            StringBuilder builder) {
        // リザーブド種類文字列の追加
        if (AwsProductName.ELASTI_CACHE == awsProductName) {
            builder.append("NodeUsage");
        } else if (AwsProductName.REDSHIFT == awsProductName) {
            builder.append("Node");
        } else if (AwsProductName.RDS == awsProductName) {
            if (isMultiAZ != null && isMultiAZ.booleanValue()) {
                builder.append("Multi-AZUsage");
            } else {
                builder.append("InstanceUsage");
            }
        } else if (AwsProductName.DYNAMO_DB == awsProductName) {
            // TODO 設計上、保留になっている箇所
            builder.append("CapacityUnit");
        } else if (AwsProductName.ELASTIC_SEARCH_SERVICE == awsProductName) {
            builder.append("ESInstance");
        }
        return true;
    }

    /**
     * インスタンスタイプを追加します。
     *
     * @param type インスタンスタイプ
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendInstanceType(String type, StringBuilder builder) {
        boolean isAppend = false;
        if (StringUtils.isEmpty(type)) {
            logger.warn("type is null or empty");
        } else {
            // インスタンスタイプの追加
            builder.append(":");
            builder.append(type);
            isAppend = true;
        }
        return isAppend;
    }

    /**
     * 要求クラスを追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param offeringClass 要求クラス
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendOfferingClass(AwsProductName awsProductName, String offeringClass,
            StringBuilder builder) {
        boolean isAppend = false;
        if (awsProductName == AwsProductName.EC2) {
            if (!StringUtils.isEmpty(offeringClass)) {
                if (builder.indexOf(":" + offeringClass) < 0) {
                    builder.append(":");
                    builder.append(offeringClass);
                    isAppend = true;
                }
            }
        }
        return isAppend;
    }

    /**
     * RIマークを追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendRI(AwsProductName awsProductName, StringBuilder builder) {
        boolean isAppend = false;
        if (awsProductName == AwsProductName.RDS || awsProductName == AwsProductName.ELASTI_CACHE
                || awsProductName == AwsProductName.REDSHIFT || awsProductName == AwsProductName.DYNAMO_DB
                || awsProductName == AwsProductName.ELASTIC_SEARCH_SERVICE) {
            // RIマークの追加
            final String RI = "(RI)";
            if (builder.indexOf(RI) < 0) {
                builder.append(RI);
                isAppend = true;
            }
        }
        return isAppend;
    }

    /**
     * 要求タイプを追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param offeringType 要求タイプ
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendOfferingType(AwsProductName awsProductName, String offeringType,
            StringBuilder builder) {
        boolean isAppend = false;
        if (awsProductName == AwsProductName.EC2 || awsProductName == AwsProductName.RDS
                || awsProductName == AwsProductName.ELASTI_CACHE || awsProductName == AwsProductName.REDSHIFT
                || awsProductName == AwsProductName.ELASTIC_SEARCH_SERVICE) {
            if (!StringUtils.isEmpty(offeringType)) {
                if (builder.indexOf(":" + offeringType) < 0) {
                    builder.append(":");
                    builder.append(offeringType);
                    isAppend = true;
                }
            }
        }
        return isAppend;
    }

    /**
     * 期間を追加します。
     *
     * @param duration 期間
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendDuration(int duration, StringBuilder builder) {
        boolean isAppend = false;
        if (duration == Duration.ONE_YEAR_SECOND.getSecond()) {
            if (builder.indexOf(":" + ONE_YEAR) < 0) {
                builder.append(":");
                builder.append(ONE_YEAR);
                isAppend = true;
            }
        } else if (duration == Duration.THREE_YEAR_SECOND.getSecond()) {
            if (builder.indexOf(":" + THREE_YEAR) < 0) {
                builder.append(":");
                builder.append(THREE_YEAR);
                isAppend = true;
            }
        } else {
            throw new CommonException("duration is wrong");
        }
        return isAppend;
    }

    /**
     * 前払いマークを追加します。
     *
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendUpfront(StringBuilder builder) {
        final String MARK = ":upfront";
        builder.append(MARK);
        return true;
    }

    /**
     * 定額月額マークを追加します。
     *
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendMonthlyStar(StringBuilder builder) {
        boolean isAppend = false;
        final String MARK = ":monthlyStar";
        if (builder.indexOf(MARK) < 0) {
            // 定額月額マークを追加
            builder.append(MARK);
            isAppend = true;
        }
        return isAppend;
    }

    /**
     * RI適用料金マークを追加します。
     *
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendMonthlyApplied(StringBuilder builder) {
        boolean isAppend = false;
        final String MARK = ":monthlyApplied";
        if (builder.indexOf(MARK) < 0) {
            // RI適用料金マークを追加
            builder.append(MARK);
            isAppend = true;
        }
        return isAppend;
    }

    /**
     * 製品種類文字列を追加します。
     *
     * @param awsProductName AWSサービス製品名
     * @param productDescription 製品説明
     * @param builder 追加するStringBuilderインスタンス
     * @return 追加したかどうか(true: 追加した, false: 追加していない)
     */
    private static boolean appendProductTypeValue(AwsProductName awsProductName, String productDescription,
            StringBuilder builder) {
        boolean isAppend = false;
        if (AwsProductName.EC2 == awsProductName || AwsProductName.RDS == awsProductName
                || AwsProductName.ELASTI_CACHE == awsProductName) {
            if (!StringUtils.isEmpty(productDescription)) {
                // 製品説明、製品種類文字列の組み合わせ情報をもつマップを取得する。
                Map<String, String> map = AppInfoUtil.getMap("ProductDescriptionTypeValueMapping");

                // 製品説明に対応する製品種類文字列を取得する。
                String productTypeValue = map.get(productDescription);
                if (StringUtils.isEmpty(productTypeValue)) {
                    throw new CommonException("productTypeValue is null or empty (productDescription="
                            + productDescription + ")");
                }
                builder.append(":");
                builder.append(productTypeValue);
                isAppend = true;
            }
        }
        return isAppend;
    }

    /**
     * オンデマンドインスタンスの使用タイプに関する情報欠落補完です。<br>
     * 補完する必要がなかった場合は、補完せずに、そのまま返却します。
     *
     * @param sourceUsageType 補完前の使用タイプ
     * @param productName 製品名
     * @param operation オペレーション
     * @return 使用タイプ
     */
    public static String missingCompletion(String sourceUsageType, String productName, String operation) {
        String usageType = sourceUsageType;
        if (AwsProductNameUtil.isEquals(AwsProductName.EC2, productName)) {
            usageType = missingCompletion(usageType, operation, mcPatterns);
        }
        return usageType;
    }

    /**
     * 同じUsageTypeで区別する必要のあるものの補完です。<br>
     * 補完する必要がなかった場合は、補完せずに、そのまま返却します。
     *
     * @param sourceUsageType 補完前の使用タイプ
     * @param productName 製品名
     * @param operation オペレーション
     * @return 使用タイプ
     */
    public static String operationCompletion(String sourceUsageType, String productName, String operation) {
        String usageType = sourceUsageType;
        AwsProductName awsProductName = AwsProductNameUtil.valueOfFromProductName(productName);
        Map<OcPatternsKind, Pattern[]> patternsMap = ocPatterns.get(awsProductName);
        if (patternsMap != null) {
            Pattern[] inclusionsPatterns = patternsMap.get(OcPatternsKind.INCLUSIONS);
            Pattern[] exclusionsPatterns = patternsMap.get(OcPatternsKind.EXCLUSIONS);
            usageType = operationCompletion(usageType, operation, inclusionsPatterns, exclusionsPatterns);
        }
        return usageType;
    }

    /**
     * リージョン名をリージョンコードに変換します。
     * 変換する必要がなかった場合は、変換せず、そのまま返却します。
     *
     * @param sourceUsageType 変換前の使用タイプ
     * @param productName 製品名
     * @return 使用タイプ
     */
    public static String changeRegionNameToRegionCode(String sourceUsageType, String productName) {
        String usageType = sourceUsageType;
        if (AwsProductNameUtil.isEquals(AwsProductName.KMS, productName)) {
            usageType = changeRegionNameToRegionCode(usageType, UsageTypeUtil.KMS_REQUESTS, crntrcPatternsForRequests);
            usageType = changeRegionNameToRegionCode(usageType, UsageTypeUtil.KMS_KEYS, crntrcPatternsForKeys);
        }
        return usageType;
    }

    /**
     * リージョン間使用に関する使用タイプの変換です。<br>
     * 変換する必要がなかった場合は、変換せず、そのまま返却します。
     *
     * @param sourceUsageType 変換前の使用タイプ
     * @return 使用タイプ
     */
    public static String changeRegionCodeToAnotherRegion(String sourceUsageType) {
        return changeRegionCodeToAnotherRegion(sourceUsageType, anotherRegionPatterns);
    }

    /**
     * アプリケーション情報を読み込み、Patternクラスの配列を生成します。
     *
     * @param category カテゴリ
     * @return Patternクラスの配列
     */
    private static Pattern[] createPattern(String category) {
        Map<String, String> patternMap = AppInfoUtil.getMap(category);
        return createPattern(patternMap.values());
    }

    /**
     * アプリケーション情報を読み込み、Patternクラスの配列を生成します。
     *
     * @param category カテゴリ
     * @return Patternクラスの配列
     */
    private static Map<AwsProductName, Map<OcPatternsKind, Pattern[]>> createPatternMap(String category) {
        Map<AwsProductName, Map<OcPatternsKind, Pattern[]>> result = new HashMap<>();
        List<Map<String, String>> list = AppInfoUtil.getListIncludingMap(category);

        for (Map<String, String> map : list) {
            String name = map.get("AwsProductName");
            AwsProductName awsProductName = AwsProductName.valueOf(name);

            List<String> inclusionsList = new ArrayList<>();
            List<String> exclusionsList = new ArrayList<>();
            Set<Entry<String, String>> entrySet = map.entrySet();
            for (Entry<String, String> entry : entrySet) {
                String key = entry.getKey();
                String value = entry.getValue();
                if (key.toUpperCase().startsWith(OcPatternsKind.INCLUSIONS.name())) {
                    inclusionsList.add(value);
                } else if (key.toUpperCase().startsWith(OcPatternsKind.EXCLUSIONS.name())) {
                    exclusionsList.add(value);
                }
            }

            Pattern[] inclusionsPatterns = createPattern(inclusionsList);
            Pattern[] exclusionsPatterns = createPattern(exclusionsList);

            Map<OcPatternsKind, Pattern[]> patternsMap = new HashMap<>();
            patternsMap.put(OcPatternsKind.INCLUSIONS, inclusionsPatterns);
            patternsMap.put(OcPatternsKind.EXCLUSIONS, exclusionsPatterns);

            result.put(awsProductName, patternsMap);
        }

        return result;
    }

    /**
     * Patternクラスの配列を生成します。
     *
     * @param collection Patternクラスの配列を生成する際に使用する文字列コレクション
     * @return Patternクラスの配列
     */
    private static Pattern[] createPattern(Collection<String> collection) {
        String[] patternValues = collection.toArray(new String[0]);
        Pattern[] patterns = new Pattern[patternValues.length];
        for (int i = 0; i < patternValues.length; i++) {
            patterns[i] = Pattern.compile(patternValues[i]);
        }
        return patterns;
    }

    /**
     * パターンクラスに設定されている正規表現に該当する使用タイプだった場合、使用タイプに「:m1.small」を追加して返却します。<br>
     * 該当するものがなかった場合は、追加せず、そのまま返却します。<br>
     * なお、オペレーションが、「Surcharge」だった場合も、追加せず、そのまま返却します。
     *
     * @param sourceUsageType 「:m1.small」追加前の使用タイプ
     * @param operation オペレーション
     * @param inclusions パターン
     * @return 使用タイプ
     */
    private static String missingCompletion(String sourceUsageType, String operation, Pattern[] inclusions) {
        String usageType = sourceUsageType;

        // オペレーションがSurchargeであるものは、現状、DedicatedUsage以外には登場しない。
        // 仮に登場した場合、m1.smallを補完してはいけないと想定されるため、
        // ここでは、<リージョンコード>-DedicatedUsage"もしくは"DedicatedUsage"だった場合という判断は含めない。
        if ("Surcharge".equals(operation)) {
            return usageType;
        }

        for (Pattern inclusion : inclusions) {
            if (inclusion.matcher(sourceUsageType).find()) {
                usageType = sourceUsageType + ":" + M1_SMALL;
                break;
            }
        }
        return usageType;
    }

    /**
     * パターンクラス(inclusions)に設定されている正規表現に該当し、パターンクラス(exclusions)に設定されている正規表現に含まれない
     * 使用タイプであり、オペレーションの値で終わっていない場合、使用タイプに「:オペレーション」を追加して返却します。<br>
     * 該当するものがなかった場合は、追加せず、そのまま返却します。
     *
     * @param sourceUsageType 「:オペレーション」追加前の使用タイプ
     * @param operation オペレーション
     * @param inclusions パターン
     * @param exclusions パターン
     * @return 使用タイプ
     */
    private static String operationCompletion(String sourceUsageType, String operation, Pattern[] inclusions,
            Pattern[] exclusions) {
        String usageType = sourceUsageType;

        for (Pattern inclusion : inclusions) {
            if (inclusion.matcher(sourceUsageType).find()) {
                if (exclusions.length == 0) {
                    if (operation != null && !sourceUsageType.endsWith(operation)) {
                        usageType = sourceUsageType + ":" + operation;
                    }
                    break;
                }
                for (Pattern exclusion : exclusions) {
                    if (!exclusion.matcher(sourceUsageType).find()) {
                        if (operation != null && !sourceUsageType.endsWith(operation)) {
                            usageType = sourceUsageType + ":" + operation;
                        }
                        break;
                    }
                }
            }
        }
        return usageType;
    }

    /**
     * パターンクラスに設定されている正規表現に該当する使用タイプだった場合、使用タイプに含まれるリージョン名を、
     * リージョンコードに変換し、返却します。<br>
     * 該当するものがなかった場合は、変換せず、そのまま返却します。
     *
     * @param sourceUsageType 変換前の使用タイプ
     * @param sourceUsageTypeSuffix 使用タイプのサフィックス
     * @param inclusions パターン
     * @return 使用タイプ
     */
    private static String changeRegionNameToRegionCode(String sourceUsageType, String usageTypeSuffix,
            Pattern[] inclusions) {
        String usageType = sourceUsageType;

        for (Pattern inclusion : inclusions) {
            if (inclusion.matcher(sourceUsageType).find()) {
                int index = sourceUsageType.indexOf("-" + usageTypeSuffix);
                if (index >= 0) {
                    String value = sourceUsageType.substring(0, index);
                    if (StringUtils.isEmpty(value)) {
                        // ワーニングログを出力し、変換せずに、そのまま返却する。
                        logger.warn("sourceUsageType substring result is null or empty(value=" + value + ")");
                    } else {
                        String regionCode = RegionUtil.getRegionCode(value);
                        if (StringUtils.isEmpty(regionCode)) {
                            // 設定が誤っている or 設定した想定内のリージョンコード以外の内容が存在した場合に発生する。
                            // ワーニングログを出力し、変換せずに、そのまま返却する。
                            logger.warn("regionCode not exist(value=" + value + ")");
                        } else {
                            usageType = regionCode + sourceUsageType.substring(index);
                        }
                    }
                } else {
                    // 通常発生しない。
                    throw new CommonException("usageTypeSuffix not exist(usageTypeSuffix=" + usageTypeSuffix + ")");
                }
                break;
            }
        }
        return usageType;
    }

    /**
     * パターンクラスに設定されている正規表現に該当する使用タイプだった場合、使用タイプに含まれる2つ目のリージョンコードを、
     * 「AnotherRegion」に変換し、返却します。
     *
     * @param sourceUsageType 変換前の使用タイプ
     * @param inclusions パターン
     * @return 使用タイプ
     */
    private static String changeRegionCodeToAnotherRegion(String sourceUsageType, Pattern[] inclusions) {
        String usageType = sourceUsageType;

        for (Pattern inclusion : inclusions) {
            if (inclusion.matcher(sourceUsageType).find()) {
                final String ANOTHER = "AnotherRegion";
                if (sourceUsageType.indexOf(ANOTHER) < 0) {
                    int startIndex = sourceUsageType.indexOf("-");
                    if (startIndex < 0) {
                        // 通常発生しない。
                        throw new CommonException("startIndex < 0");
                    }
                    String endValue = sourceUsageType.substring(startIndex + 1);
                    int endIndex = endValue.indexOf("-");
                    if (endIndex < 0) {
                        // 通常発生しない。
                        throw new CommonException("endIndex < 0");
                    }

                    usageType = sourceUsageType.substring(0, startIndex + 1);
                    usageType = usageType + ANOTHER;
                    usageType = usageType + endValue.substring(endIndex);
                }
            }
        }
        return usageType;
    }
}
